home *** CD-ROM | disk | FTP | other *** search
/ EnigmA Amiga Run 1995 November / EnigmA AMIGA RUN 02 (1995)(G.R. Edizioni)(IT)[!][issue 1995-11][Skylink CD].iso / earcd / unix / mp14tar.z / mp14tar / mpack / decode.c < prev    next >
C/C++ Source or Header  |  1994-06-01  |  24KB  |  982 lines

  1. /*
  2.  * Decode MIME parts.
  3.  */
  4. /* (C) Copyright 1993 by John G. Myers
  5.  * All Rights Reserved.
  6.  *
  7.  * Permission to use, copy, modify, distribute, and sell this software
  8.  * and its documentation for any purpose is hereby granted without
  9.  * fee, provided that the above copyright notice appear in all copies
  10.  * and that both that copyright notice and this permission notice
  11.  * appear in supporting documentation, and that the name of John G.
  12.  * Myers not be used in advertising or publicity pertaining to
  13.  * distribution of the software without specific, written prior
  14.  * permission.  John G. Myers makes no representations about the
  15.  * suitability of this software for any purpose.  It is provided "as
  16.  * is" without express or implied warranty.
  17.  *
  18.  * JOHN G. MYERS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
  19.  * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  20.  * FITNESS, IN NO EVENT SHALL JOHN G. MYERS BE LIABLE FOR ANY SPECIAL,
  21.  * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  22.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
  23.  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
  24.  * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  25.  */
  26.  
  27. #include <stdio.h>
  28. #include <string.h>
  29. #include <ctype.h>
  30. #include "xmalloc.h"
  31. #include "common.h"
  32.  
  33. extern char *os_idtodir();
  34. extern FILE *os_newtypedfile();
  35. extern char *md5digest();
  36.  
  37. /* List of pending multipart boundaries */
  38. struct boundary {
  39.     char **id;
  40.     int count;
  41.     int alloc;
  42. };
  43.  
  44. /* The possible content transfer encodings */
  45. enum encoding { enc_none, enc_binary, enc_qp, enc_base64 };
  46.  
  47. typedef char **params;
  48.  
  49. char *ParseHeaders();
  50. enum encoding parseEncoding();
  51. params ParseContent();
  52. char *getParam();
  53. char *getDispositionFilename();
  54.  
  55. /*
  56.  * Read and handle an RFC 822 message from the file 'infile'.
  57.  */
  58. handleMessage(infile, defaultContentType, inAppleDouble, extractText,
  59.           boundaries)
  60. FILE *infile;
  61. char *defaultContentType;
  62. int inAppleDouble;
  63. int extractText;
  64. struct boundary *boundaries;
  65. {
  66.     char *headers, *subject, *contentType, *contentDisposition, *contentMD5;
  67.     enum encoding contentEncoding;
  68.     params contentParams;
  69.     struct boundary newboundaries;
  70.     int depth;
  71.  
  72.     /* No passed-in boundary structure, create a new one */
  73.     if (!boundaries) {
  74.     boundaries = &newboundaries;
  75.     boundaries->id = 0;
  76.     boundaries->alloc = boundaries->count = 0;
  77.     }
  78.  
  79.     /* Parse the headers, getting the ones we're interested in */
  80.     headers = ParseHeaders(infile, &subject, &contentType, &contentEncoding,
  81.                &contentDisposition, &contentMD5, boundaries);
  82.     if (!headers) return 1;
  83.  
  84.     /* If no content type, or a non-MIME content type, use the default */
  85.     if (!contentType || !strchr(contentType, '/')) {
  86.     contentType = defaultContentType;
  87.     }
  88.     contentParams = ParseContent(&contentType);
  89.  
  90.     if (!cistrcmp(contentType, "message/rfc822")) {
  91.     if (contentEncoding != enc_none && contentEncoding != enc_binary) {
  92.         warn("ignoring invalid content encoding on message/rfc822");
  93.     }
  94.  
  95.     /* Simple recursion */
  96.     return handleMessage(infile, "text/plain", 0, extractText, boundaries);
  97.     }
  98.     else if (!cistrcmp(contentType, "message/partial")) {
  99.     if (contentEncoding != enc_none && contentEncoding != enc_binary) {
  100.         warn("ignoring invalid content encoding on message/partial");
  101.     }
  102.     return handlePartial(infile, headers, contentParams, extractText,
  103.                  boundaries);
  104.     }
  105.     else if (!cistrncmp(contentType, "message/", 8)) {
  106.     /* Probably message/external.  We don't care--toss it */
  107.     return ignoreMessage(infile, boundaries);
  108.     }
  109.     else if (!cistrncmp(contentType, "multipart/", 10)) {
  110.     if (contentEncoding != enc_none && contentEncoding != enc_binary) {
  111.         warn("ignoring invalid content encoding on multipart");
  112.     }
  113.     return handleMultipart(infile, contentType, contentParams,
  114.                    extractText, boundaries);
  115.     }
  116.     else if (!boundaries->count && !cistrncmp(contentType, "text/", 5)) {
  117.     /* top-level text message, handle as possible uuencoded file */
  118.     return handleUuencode(infile, subject);
  119.     }
  120.     else if (!extractText && !inAppleDouble &&
  121.          !getDispositionFilename(contentDisposition) &&
  122.          !getParam(contentParams, "name") &&
  123.          !cistrncmp(contentType, "text/", 5)) {
  124.     return handleText(infile, contentEncoding, boundaries);
  125.     }
  126.     else {
  127.     /* Some sort of attachment, extract it */
  128.     return saveToFile(infile, inAppleDouble, contentType, contentParams,
  129.               contentEncoding, contentDisposition, contentMD5,
  130.               boundaries);
  131.     }
  132. }
  133.  
  134. /*
  135.  * Skip whitespace and RFC-822 comments.
  136.  */
  137. SkipWhitespace(s)
  138. char **s;
  139. {
  140.     char *p = *s;
  141.     int commentlevel = 0;
  142.  
  143.     while (*p && (isspace(*p) || *p == '(')) {
  144.     if (*p == '\n') {
  145.         p++;
  146.         if (*p != ' ' && *p != '\t') {
  147.         *s = 0;
  148.         return;
  149.         }
  150.     }
  151.     else if (*p == '(') {
  152.         p++;
  153.         commentlevel++;
  154.         while (commentlevel) {
  155.         switch (*p) {
  156.         case '\n':
  157.             p++;
  158.             if (*p == ' ' || *p == '\t') break;
  159.             /* FALL THROUGH */
  160.         case '\0':
  161.             *s = 0;
  162.             return;
  163.             
  164.         case '\\':
  165.             p++;
  166.             break;
  167.  
  168.         case '(':
  169.             commentlevel++;
  170.             break;
  171.  
  172.         case ')':
  173.             commentlevel--;
  174.             break;
  175.         }
  176.         p++;
  177.         }
  178.     }
  179.     else p++;
  180.     }
  181.     if (*p == 0) {
  182.     *s = 0;
  183.     }
  184.     else {
  185.     *s = p;
  186.     }
  187. }
  188.  
  189. /*
  190.  * Read and parse the headers of an RFC 822 message, returning them in
  191.  * a pointer to a static buffer.  The headers are read from 'infile'.
  192.  * A pointer to the value of any Subject:, Content-Type:,
  193.  * Content-Disposition:, or Content-MD5: header is stored in the space
  194.  * pointed to by 'subjectp', 'contentTypep', contentDispositionp, and
  195.  * contentMD5p, respectively.  The Content-Transfer-Encoding is stored
  196.  * in the enum pointed to by 'contentEncodingp'.
  197.  */
  198. #define HEADGROWSIZE 1000
  199. char *ParseHeaders(infile, subjectp, contentTypep, contentEncodingp,
  200.            contentDispositionp, contentMD5p, boundaries)
  201. FILE *infile;
  202. char **subjectp, **contentTypep;
  203. enum encoding *contentEncodingp;
  204. char **contentDispositionp, **contentMD5p;
  205. struct boundary *boundaries;
  206. {
  207.     static int alloced = 0;
  208.     static char *headers;
  209.     int left, len;
  210.     char *next, *val;
  211.  
  212.     /* Read headers into buffer pointed to by "headers" */
  213.     if (!alloced) {
  214.     headers = xmalloc(alloced = HEADGROWSIZE);
  215.     }
  216.     next = headers;
  217.     *next++ = '\n';        /* Leading newline to make matching header names easier */
  218.     left = alloced - 2;        /* Allow room for terminating null */
  219.  
  220.     while (fgets(next, left, infile) && (*next != '\n' || next[-1] != '\n')) {
  221.     len = strlen(next);
  222.     left -= len;
  223.     next += len;
  224.  
  225.     if (next[-1] == '\n' && *next == '-' &&
  226.         PendingBoundary(next, boundaries->id, &boundaries->count)) {
  227.         return 0;
  228.     }
  229.  
  230.     if (left < 100) {
  231.         len = next - headers;
  232.         alloced += HEADGROWSIZE;
  233.         left += HEADGROWSIZE;
  234.         headers = xrealloc(headers, alloced);
  235.         next = headers + len;
  236.     }
  237.     }
  238.  
  239.     *next = '\0';
  240.  
  241.     /* Look for the headers we find particularly interesting */
  242.     *subjectp = *contentTypep = *contentDispositionp = *contentMD5p = 0;
  243.     *contentEncodingp = enc_none;
  244.     for (next = headers; *next; next++) {
  245.     if (*next == '\n') {
  246.         switch(next[1]) {
  247.         case 's':
  248.         case 'S':
  249.         if (!cistrncmp(next+2, "ubject:", 7)) {
  250.             val = next+9;
  251.             SkipWhitespace(&val);
  252.             if (val) *subjectp = val;
  253.         }
  254.         break;
  255.  
  256.         case 'c':
  257.         case 'C':
  258.         if (!cistrncmp(next+2, "ontent-type:", 12)) {
  259.             val = next+14;
  260.             SkipWhitespace(&val);
  261.             if (val) *contentTypep = val;
  262.         }
  263.         else if (!cistrncmp(next+2, "ontent-transfer-encoding:", 25)) {
  264.             *contentEncodingp = parseEncoding(next+27);
  265.         }
  266.         else if (!cistrncmp(next+2, "ontent-disposition:", 19)) {
  267.             val = next+21;
  268.             SkipWhitespace(&val);
  269.             if (val) *contentDispositionp = val;
  270.         }
  271.         else if (!cistrncmp(next+2, "ontent-md5:", 11)) {
  272.             val = next+13;
  273.             SkipWhitespace(&val);
  274.             if (val) *contentMD5p = val;
  275.         }
  276.         }
  277.     }
  278.     }
  279.     return headers;
  280. }
  281.  
  282. /*
  283.  * Parse the Content-Transfer-Encoding: value pointed to by 's'.
  284.  * Returns the appropriate encoding enum.
  285.  */
  286. enum encoding parseEncoding(s)
  287. char *s;
  288. {
  289.     SkipWhitespace(&s);
  290.     if (s) {
  291.     switch (*s) {
  292.     case 'q':
  293.     case 'Q':
  294.         if (!cistrncmp(s+1, "uoted-printable", 15) &&
  295.         (isspace(s[16]) || s[16] == '(')) {
  296.         return enc_qp;
  297.         }
  298.         break;
  299.  
  300.     case '7':
  301.     case '8':
  302.         if (!cistrncmp(s+1, "bit", 3) &&
  303.         (isspace(s[4]) || s[4] == '(')) {
  304.         return enc_none;
  305.         }
  306.         break;
  307.  
  308.     case 'b':
  309.     case 'B':
  310.         if (!cistrncmp(s+1, "ase64", 5) &&
  311.         (isspace(s[6]) || s[6] == '(')) {
  312.         return enc_base64;
  313.         }
  314.         if (!cistrncmp(s+1, "inary", 5) &&
  315.         (isspace(s[6]) || s[6] == '(')) {
  316.         return enc_binary;
  317.         }
  318.     }
  319.     warn("ignoring unknown content transfer encoding\n");    
  320.     }
  321.     return enc_none;
  322. }
  323.  
  324. /*
  325.  * Parse the value of a Content-Type: header.
  326.  * 'headerp' points to a pointer to the input string.
  327.  * The pointer pointed to by 'headerp' is changed to point to
  328.  * a static buffer containing the content type stripped of whitespace
  329.  * and parameters.  The parameters are converted to a type suitable for
  330.  * getParm() and returned.
  331.  */
  332. #define PARAMGROWSIZE 10
  333. params ParseContent(headerp)
  334. char **headerp;
  335. {
  336.     char *header;
  337.     static int palloced = 0;
  338.     static char **param;
  339.     static int calloced = 0;
  340.     static char *cbuf;
  341.     char *p;
  342.     int nparam;
  343.  
  344.     p = header = *headerp;
  345.  
  346.     /* Find end of header, including continuation lines */
  347.     do {
  348.     p = strchr(p+1, '\n');
  349.     } while (p && isspace(p[1]));
  350.     if (!p) {
  351.     p = header + strlen(header);
  352.     }
  353.  
  354.     /* If necessary, allocate/grow cbuf to hold header. */
  355.     if (p - header >= calloced) {
  356.     calloced = p - header + 1;
  357.     if (calloced < 200) calloced = 200;
  358.     cbuf = xrealloc(cbuf, calloced);
  359.     }
  360.  
  361.     /* Copy header to cbuf */
  362.     strncpy(cbuf, header, p - header);
  363.     cbuf[p - header] = 0;
  364.     header = *headerp = cbuf;
  365.     
  366.     nparam = 0;
  367.  
  368.     /* Strip whitespace from content type */
  369.     /* ParseHeaders() stripped leading whitespace */
  370.     p = header;
  371.     while (header && *header && *header != ';') {
  372.     while (*header && !isspace(*header) && *header != '(' &&
  373.            *header != ';') {
  374.         *p++ = *header++;
  375.     }
  376.     SkipWhitespace(&header);
  377.     }
  378.     if (!header || !*header) return 0;
  379.     header++;
  380.     *p = '\0';
  381.     
  382.     /* Parse the parameters */
  383.     while (*header) {
  384.     SkipWhitespace(&header);
  385.     if (!header) break;
  386.  
  387.     if (nparam+1 >= palloced) {
  388.         palloced += PARAMGROWSIZE;
  389.         param = (char **) xrealloc((char *)param, palloced * sizeof(char *));
  390.     }
  391.     param[nparam++] = header;
  392.  
  393.     /* Find any separating semicolon.  Pay attention to quoted-strings */
  394.     while (*header && *header != ';') {
  395.         if (*header == '\"') {
  396.         ++header;
  397.         while (*header && *header != '\"') {
  398.             if (*header == '\\') {
  399.             ++header;
  400.             if (!*header) break;
  401.             }
  402.             ++header;
  403.         }
  404.         if (!*header) break;
  405.         }
  406.         else if (*header == '(') {
  407.         /* Convert comments to spaces */
  408.         p = header;
  409.         SkipWhitespace(&p);
  410.         if (!p) {
  411.             break;
  412.         }
  413.         while (header < p) *header++ = ' ';
  414.         header--;
  415.         }
  416.         header++;
  417.     }
  418.     if (*header) *header++ = '\0';
  419.     }
  420.     param[nparam] = 0;
  421.     return param;
  422. }
  423.  
  424. /*
  425.  * Get the value of the parameter named 'key' from the content-type
  426.  * parameters 'cParams'.  Returns a pointer to a static bufer which
  427.  * contains the value, or null if no such parameter was found.
  428.  */
  429. #define VALUEGROWSIZE 100
  430. char *getParam(cParams, key)
  431. params cParams;
  432. char *key;
  433. {
  434.     static char *value;
  435.     static int alloced = 0;
  436.     int left;
  437.     int keylen = strlen(key);
  438.     char *from, *to;
  439.  
  440.     if (!cParams) return 0;
  441.  
  442.     if (!alloced) {
  443.     value = xmalloc(alloced = VALUEGROWSIZE);
  444.     }
  445.  
  446.     /* Find the named parameter */
  447.     while (*cParams) {
  448.     if (!cistrncmp(key, *cParams, keylen) &&
  449.         ((*cParams)[keylen] == '=' || isspace((*cParams)[keylen]))) break;
  450.     cParams++;
  451.     }
  452.     if (!*cParams) return 0;
  453.  
  454.     /* Skip over the "=" and any surrounding whitespace */
  455.     from = *cParams + keylen;
  456.     while (*from && isspace(*from)) from++;
  457.     if (*from++ != '=') return 0;
  458.     while (*from && isspace(*from)) from++;
  459.     if (!*from) return 0;
  460.  
  461.     /* Copy value into buffer */
  462.     to = value;
  463.     left = alloced - 1;
  464.     if (*from == '\"') {
  465.     /* Quoted-string */
  466.     from++;
  467.     while (*from && *from != '\"') {
  468.         if (!--left) {
  469.         alloced += VALUEGROWSIZE;
  470.         value = xrealloc(value, alloced);
  471.         to = value + alloced - left - 2;
  472.         }
  473.         if (*from == '\\') {
  474.         from++;
  475.         if (!*from) return 0;
  476.         }
  477.         *to++ = *from++;
  478.     }
  479.     if (!*from) return 0;
  480.     }
  481.     else {
  482.     /* Just a token */
  483.     while (*from && !isspace(*from)) {
  484.         if (!--left) {
  485.         alloced += VALUEGROWSIZE;
  486.         value = xrealloc(value, alloced);
  487.         to = value + alloced - left - 2;
  488.         }
  489.         *to++ = *from++;
  490.     }
  491.     }
  492.     *to = '\0';
  493.     return value;
  494. }
  495.  
  496. /*
  497.  * Get the value of the "filename" parameter in a Content-Disposition:
  498.  * header.  Returns a pointer to a static buffer containing the value, or
  499.  * a null pointer if there was no such parameter.
  500.  */
  501. char *
  502. getDispositionFilename(disposition)
  503. char *disposition;
  504. {
  505.     static char *value;
  506.     static int alloced = 0;
  507.     int left;
  508.     char *to;
  509.  
  510.     if (!disposition) return 0;
  511.  
  512.     /* Skip until we find ";" "filename" "=" tokens. */
  513.     for (;;) {
  514.     /* Skip until we find ";" */
  515.     while (*disposition != ';') {
  516.         if (!*disposition) return 0;
  517.         else if (*disposition == '\"') {
  518.         ++disposition;
  519.         while (*disposition && *disposition != '\"') {
  520.             if (*disposition == '\\') {
  521.             ++disposition;
  522.             if (!*disposition) return 0;
  523.             }
  524.             ++disposition;
  525.         }
  526.         if (!*disposition) return 0;
  527.         }
  528.         else if (*disposition == '(') {
  529.         SkipWhitespace(&disposition);
  530.         if (!disposition) return 0;
  531.         disposition--;
  532.         }
  533.         disposition++;
  534.     }
  535.  
  536.     /* Skip over ";" and trailing whitespace */
  537.     disposition++;
  538.     SkipWhitespace(&disposition);
  539.     if (!disposition) return 0;
  540.  
  541.     /*
  542.      * If we're not looking at a "filename" token, go back
  543.      * and look for another ";".  Otherwise skip it and
  544.      * trailing whitespace.
  545.      */
  546.     if (cistrncmp(disposition, "filename", 8) != 0) continue;
  547.     disposition += 8;
  548.     if (!isspace(*disposition) && *disposition != '=' &&
  549.         *disposition != '(') {
  550.         continue;
  551.     }
  552.     SkipWhitespace(&disposition);
  553.     if (!disposition) return 0;
  554.  
  555.     /* If we're looking at a ";", we found what we're looking for */
  556.     if (*disposition++ == '=') break;
  557.     }
  558.  
  559.     SkipWhitespace(&disposition);
  560.     if (!disposition) return 0;
  561.       
  562.     if (!alloced) {
  563.     value = xmalloc(alloced = VALUEGROWSIZE);
  564.     }
  565.  
  566.     /* Copy value into buffer */
  567.     to = value;
  568.     left = alloced - 1;
  569.     if (*disposition == '\"') {
  570.     /* Quoted-string */
  571.     disposition++;
  572.     while (*disposition && *disposition != '\"') {
  573.         if (!--left) {
  574.         alloced += VALUEGROWSIZE;
  575.         value = xrealloc(value, alloced);
  576.         to = value + alloced - left - 2;
  577.         }
  578.         if (*disposition == '\\') {
  579.         disposition++;
  580.         if (!*disposition) return 0;
  581.         }
  582.         *to++ = *disposition++;
  583.     }
  584.     if (!*disposition) return 0;
  585.     }
  586.     else {
  587.     /* Just a token */
  588.     while (*disposition && !isspace(*disposition) &&
  589.            *disposition != '(') {
  590.         if (!--left) {
  591.         alloced += VALUEGROWSIZE;
  592.         value = xrealloc(value, alloced);
  593.         to = value + alloced - left - 2;
  594.         }
  595.         *to++ = *disposition++;
  596.     }
  597.     }
  598.     *to = '\0';
  599.     return value;
  600. }    
  601.  
  602. /*
  603.  * Read and handle a message/partial object from the file 'infile'.
  604.  */
  605. handlePartial(infile, headers, contentParams, extractText, boundaries)
  606. FILE *infile;
  607. char *headers;
  608. params contentParams;
  609. int extractText;
  610. struct boundary *boundaries;
  611. {
  612.     char *id, *dir, *p;
  613.     int thispart;
  614.     int nparts = 0;
  615.     char buf[1024];
  616.     FILE *partfile, *outfile;
  617.     int i, docopy;
  618.  
  619.     id = getParam(contentParams, "id");
  620.     if (!id) {
  621.     warn("partial message has no id parameter");
  622.     goto ignore;
  623.     }
  624.  
  625.     /* Get directory to store the parts being reassembled */
  626.     dir = os_idtodir(id);
  627.     if (!dir) goto ignore;
  628.  
  629.     p = getParam(contentParams, "number");
  630.     if (!p) {
  631.     warn("partial message doesn't have number parameter");
  632.     goto ignore;
  633.     }
  634.     thispart = atoi(p);
  635.  
  636.     if (p = getParam(contentParams, "total")) {
  637.     nparts = atoi(p);
  638.     if (nparts <= 0) {
  639.         warn("partial message has invalid number of parts");
  640.         goto ignore;
  641.     }
  642.     /* Store number of parts in reassembly directory */
  643.     sprintf(buf, "%sCT", dir);
  644.     partfile = fopen(buf, "w");
  645.     if (!partfile) {
  646.         os_perror(buf);
  647.         goto ignore;
  648.     }
  649.     fprintf(partfile, "%d\n", nparts);
  650.     fclose(partfile);
  651.     }
  652.     else {
  653.     /* Try to retrieve number of parts from reassembly directory */
  654.     sprintf(buf, "%sCT", dir);
  655.     if (partfile = fopen(buf, "r")) {
  656.         if (fgets(buf, sizeof(buf), partfile)) {
  657.         nparts = atoi(buf);
  658.         if (nparts < 0) nparts = 0;
  659.         }
  660.         fclose(partfile);
  661.     }
  662.     }
  663.  
  664.     /* Sanity check */
  665.     if (thispart <= 0 || (nparts && thispart > nparts)) {
  666.     warn("partial message has invalid number");
  667.     goto ignore;
  668.     }
  669.  
  670.     sprintf(buf, "Saving %s part %d", getParam(contentParams, "id"), thispart);
  671.     if (nparts) sprintf(buf+strlen(buf), " of %d", nparts);
  672.     chat(buf);
  673.  
  674.     /* Create file to store this part */
  675.     sprintf(buf, "%s%d", dir, thispart);
  676.     partfile = fopen(buf, "w");
  677.     if (!partfile) {
  678.     os_perror(buf);
  679.     goto ignore;
  680.     }
  681.  
  682.     /* Do special-case header handling for first part */
  683.     if (thispart == 1) {
  684.     int skippedfirstbyte = 0;
  685.  
  686.     while (*headers) {
  687.         if (*headers == '\n' &&
  688.         (!cistrncmp(headers, "\ncontent-", 9) ||
  689.          !cistrncmp(headers, "\nmessage-id:", 12))) {
  690.         /* Special case, skip header */
  691.         headers++;
  692.         while (*headers && (*headers != '\n' || isspace(headers[1]))) {
  693.             headers++;
  694.         }
  695.         }
  696.         else {
  697.         /* First byte of headers is extra newline, don't write it to file */
  698.         if (skippedfirstbyte++)    putc(*headers, partfile);
  699.         headers++;
  700.         }
  701.     }
  702.     docopy = 0;
  703.     /* Handle headers in the multipart/partial body */
  704.     while (fgets(buf, sizeof(buf), infile)) {
  705.         if (*buf == '\n') {
  706.         putc('\n', partfile);
  707.         break;
  708.         }
  709.         if (!cistrncmp(buf, "content-", 8) || !cistrncmp(buf, "message-id:", 11)) {
  710.         docopy = 1;
  711.         }
  712.         else if (!isspace(*buf)) {
  713.         docopy = 0;
  714.         }
  715.  
  716.         if (docopy) fputs(buf, partfile);
  717.         while(buf[strlen(buf)-1] != '\n' && fgets(buf, sizeof(buf), infile)) {
  718.         if (docopy) fputs(buf, partfile);
  719.         }
  720.     }
  721.     }
  722.  
  723.     /* Copy the contents to the file */
  724.     while (fgets(buf, sizeof(buf), infile) &&
  725.        !PendingBoundary(buf, boundaries->id, &boundaries->count)) {
  726.     fputs(buf, partfile);
  727.     }
  728.     fclose(partfile);
  729.  
  730.     /* Check to see if we have all parts.  Start from the highest numbers
  731.      * as we are more likely not to have them.
  732.      */
  733.     for (i = nparts; i; i--) {
  734.     sprintf(buf, "%s%d", dir, i);
  735.     partfile = fopen(buf, "r");
  736.     if (partfile) {
  737.         fclose(partfile);
  738.     }
  739.     else {
  740.         break;
  741.     }
  742.     }
  743.  
  744.     if (i || !nparts) {
  745.     /* We don't have all the parts yet */
  746.     return 0;
  747.     }
  748.  
  749.     /* We have everything, concatenate all the parts into a single file */
  750.     sprintf(buf, "%sFULL", dir);
  751.     outfile = fopen(buf, "w");
  752.     if (!outfile) {
  753.     os_perror(buf);
  754.     return 1;
  755.     }
  756.     for (i=1; i<=nparts; i++) {
  757.     sprintf(buf, "%s%d", dir, i);
  758.     partfile = fopen(buf, "r");
  759.     if (!partfile) {
  760.         os_perror(buf);
  761.         return 1;
  762.     }
  763.     while (fgets(buf, sizeof(buf), partfile)) {
  764.         fputs(buf, outfile);
  765.     }
  766.     fclose(partfile);
  767.  
  768.     /* Done with message part file, delete it */
  769.     sprintf(buf, "%s%d", dir, i);
  770.     remove(buf);
  771.     }
  772.  
  773.     /* Open the concatenated file for reading and handle it */
  774.     fclose(outfile);
  775.     sprintf(buf, "%sFULL", dir);
  776.     outfile = fopen(buf, "r");
  777.     if (!outfile) {
  778.     os_perror(buf);
  779.     return 1;
  780.     }
  781.     handleMessage(outfile, "text/plain", 0, extractText, (struct boundary *)0);
  782.     fclose(outfile);
  783.  
  784.     /* Clean up the rest of the reassembly directory */
  785.     sprintf(buf, "%sFULL", dir);
  786.     remove(buf);
  787.     sprintf(buf, "%sCT", dir);
  788.     remove(buf);
  789.     os_donewithdir(dir);
  790.  
  791.     return 0;
  792.  
  793.  ignore:
  794.     ignoreMessage(infile, boundaries);
  795.     return 1;
  796. }
  797.  
  798. /*
  799.  * Skip over a message object from the file 'infile'.
  800.  */
  801. ignoreMessage(infile, boundaries)
  802. FILE *infile;
  803. struct boundary *boundaries;
  804. {
  805.     char buf[1024];
  806.  
  807.     while (fgets(buf, sizeof(buf), infile) &&
  808.        !PendingBoundary(buf, boundaries->id, &boundaries->count));
  809.     return 0;
  810. }
  811.  
  812. /*
  813.  * Read and handle a multipart object from the file 'infile'.
  814.  */
  815. handleMultipart(infile, contentType, contentParams, extractText, boundaries)
  816. FILE *infile;
  817. char *contentType;
  818. params contentParams;
  819. int extractText;
  820. struct boundary *boundaries;
  821. {
  822.     char *id;
  823.     char *defaultContentType = "text/plain";
  824.     int depth;
  825.     int isAppleDouble = 0;
  826.  
  827.     /* Components of multipart/digest have a different default content-type */
  828.     if (!cistrcmp(contentType, "multipart/digest")) {
  829.     defaultContentType = "message/rfc822";
  830.     }
  831.     if (!cistrcmp(contentType, "multipart/appledouble")) {
  832.     isAppleDouble++;
  833.     }
  834.  
  835.     if (!(id = getParam(contentParams, "boundary"))) {
  836.     warn("multipart message has no boundary parameter");
  837.     id="";
  838.     }
  839.  
  840.     /* Expand boundaries array if necessary */
  841.     if (boundaries->count == boundaries->alloc) {
  842.     boundaries->alloc += 20;
  843.     boundaries->id = (char **)xrealloc((char *)boundaries->id,
  844.                        boundaries->alloc * sizeof(char *));
  845.     }
  846.     
  847.     /* Add the new boundary id */
  848.     boundaries->id[boundaries->count++] = strsave(id);
  849.     depth = boundaries->count;
  850.  
  851.     /* Skip over preamble */
  852.     ignoreMessage(infile, boundaries);
  853.  
  854.     /* Handle the component messages */
  855.     while (boundaries->count == depth) {
  856.     handleMessage(infile, defaultContentType, isAppleDouble, extractText,
  857.               boundaries);
  858.     }
  859.  
  860.     /* Skip over postamble */
  861.     if (boundaries->count == depth-1) {
  862.     ignoreMessage(infile, boundaries);
  863.     }
  864.     
  865.     /* Remove any lingering unused description file */
  866.     (void) remove(TEMPFILENAME);
  867.  
  868.     return 0;
  869. }
  870.  
  871. /*
  872.  * Handle a text message object from the file 'infile' by saving it to
  873.  * the temporary description file.
  874.  */
  875. int handleText(infile, contentEncoding, boundaries)
  876. FILE *infile;
  877. enum encoding contentEncoding;
  878. struct boundary *boundaries;
  879. {
  880.     FILE *descfile;
  881.  
  882.     descfile = fopen(TEMPFILENAME, "w");
  883.     if (!descfile) {
  884.     os_perror(TEMPFILENAME);
  885.     ignoreMessage(infile, boundaries);
  886.     return 1;
  887.     }
  888.  
  889.     /* Write the file, handling the appropriate encoding */
  890.     switch (contentEncoding) {
  891.     case enc_none:
  892.     case enc_binary:
  893.     fromnone(infile, descfile, (char **)0, boundaries->id,
  894.          &boundaries->count);
  895.     break;
  896.  
  897.     case enc_qp:
  898.     fromqp(infile, descfile, (char **)0, boundaries->id,
  899.            &boundaries->count);
  900.     break;
  901.  
  902.     case enc_base64:
  903.     from64(infile, descfile, (char **)0, 1, boundaries->id,
  904.            &boundaries->count);
  905.     break;
  906.     }
  907.  
  908.     fclose(descfile);
  909.     return 0;
  910. }
  911.  
  912. /*
  913.  * Read a message object from 'infile' and save it to a file.
  914.  */
  915. saveToFile(infile, inAppleDouble, contentType, contentParams,
  916.        contentEncoding, contentDisposition, contentMD5, boundaries)
  917. FILE *infile;
  918. int inAppleDouble;
  919. char *contentType;
  920. params contentParams;
  921. enum encoding contentEncoding;
  922. char *contentDisposition, *contentMD5;
  923. struct boundary *boundaries;
  924. {
  925.     FILE *outfile = 0;
  926.     int flags = 0;
  927.     int suppressCR = 0;
  928.     char *outputmd5;
  929.     char *fname;
  930.  
  931.     /* Find an appropriate filename and create the output file */
  932.     if (contentEncoding == enc_base64 && !cistrncmp(contentType, "text/", 5)) {
  933.     suppressCR = 1;
  934.     }
  935.     else if (contentEncoding == enc_base64 || contentEncoding == enc_binary) {
  936.     flags |= FILE_BINARY;
  937.     }
  938.  
  939.     if (inAppleDouble) flags |= FILE_INAPPLEDOUBLE;
  940.     
  941.     fname = getDispositionFilename(contentDisposition);
  942.     if (!fname) fname = getParam(contentParams, "name");
  943.     outfile = os_newtypedfile(fname, contentType, flags);
  944.     if (!outfile) {
  945.     ignoreMessage(infile, boundaries);
  946.     return 1;
  947.     }
  948.  
  949.     /* Write the file, handling the appropriate encoding */
  950.     switch (contentEncoding) {
  951.     case enc_none:
  952.     case enc_binary:
  953.     fromnone(infile, outfile, &outputmd5, boundaries->id,
  954.          &boundaries->count);
  955.     break;
  956.  
  957.     case enc_qp:
  958.     fromqp(infile, outfile, &outputmd5, boundaries->id,
  959.            &boundaries->count);
  960.     break;
  961.  
  962.     case enc_base64:
  963.     from64(infile, outfile, &outputmd5, suppressCR, boundaries->id,
  964.            &boundaries->count);
  965.     break;
  966.     }
  967.     rewind(outfile);
  968.  
  969.     /* Check the MD5 digest if it was supplied */
  970.     if (contentMD5) {
  971.     if (strncmp(outputmd5, contentMD5, strlen(outputmd5)) != 0) {
  972.         os_warnMD5mismatch();
  973.     }
  974.     }
  975.     free(outputmd5);
  976.  
  977.     os_closetypedfile(outfile);
  978.     return 0;
  979. }
  980.  
  981.     
  982.